package com.yangjie.autotest.component;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.beanutils.MethodUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.yangjie.autotest.component.xml.Component;
import com.yangjie.autotest.component.xml.Components;
import com.yangjie.autotest.component.xml.InitMethod;
import com.yangjie.autotest.component.xml.Param;
import com.yangjie.autotest.exception.ValidateFailedException;
import com.yangjie.log.LoggerIF;


public class ComponentManager implements ApplicationContextAware {
	private ApplicationContext context = null;
	@Inject
	private LoggerIF logger = null;
	
	private String componentDir = null;
	
	private String buildinCompConfig = null;
	
	@Inject
	private ComponentReader componentReader = null; 
	
	public String getClassloaderHierarchy(ClassLoader cl) {
		StringBuffer sb = new StringBuffer();
		while(cl!= null) {
			sb.append(cl.getClass().getName()).append(";");
			cl = cl.getParent();			
		}
		return sb.toString();
	}
	
	public LoggerIF getLogger() {
		return logger;
	}

	public void setLogger(LoggerIF logger) {
		this.logger = logger;
	}

	public ComponentReader getComponentReader() {
		return componentReader;
	}

	public void setComponentReader(ComponentReader componentReader) {
		this.componentReader = componentReader;
	}

	public Map<String, Component> getComponentDefinitionMap() {
		return componentDefinitionMap;
	}

	public void setComponentDefinitionMap(Map<String, Component> componentDefinitionMap) {
		this.componentDefinitionMap = componentDefinitionMap;
	}

	public Map<String, Object> getComponentInstanceMap() {
		return componentInstanceMap;
	}

	public void setComponentInstanceMap(Map<String, Object> componentInstanceMap) {
		this.componentInstanceMap = componentInstanceMap;
	}

	private Map<String, Component> componentDefinitionMap = new HashMap<String, Component>();
	private Map<String, Object> componentInstanceMap = new HashMap<String, Object>();
	
	public String getBuildinCompConfig() {
		return buildinCompConfig;
	}

	public void setBuildinCompConfig(String buildinCompConfig) {
		this.buildinCompConfig = buildinCompConfig;
	}

	public String getComponentDir() {
		return componentDir;
	}

	public void setComponentDir(String componentDir) {
		this.componentDir = componentDir;
	}
	
	public void init() {
		loadComponentDefinition();
		for(String key: componentDefinitionMap.keySet()) {
			Object componentObj = initComponent(componentDefinitionMap.get(key));
			componentInstanceMap.put(key, componentObj);
		}
	}
	
	public Object getComponentInstance(String name) {
		return componentInstanceMap.get(name);
	}
	
	public Object initComponent(Component component) {
		Object componentObj = null;
		if(component.isBuildin()) {
			componentObj = initBuildinComponent(component);
		} else {
			componentObj = initExternalComponent(component);
		}
		return componentObj;
	}

	private Object initExternalComponent(Component component) {
		Object componentObj = null;
		String definitionFilePath = component.getDefinitionFile();
		File file = new File(definitionFilePath);
		String componentDir = file.getParent();
		file = new File(componentDir);
		File[] jarFiles = file.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				// TODO Auto-generated method stub
				if(pathname.getName().endsWith(".jar")) {
					return true;
				}
				return false;
			}
			
		});
		
//		String[] libFiles = file.list();
		URL[] urls = new URL[jarFiles.length];
		for (int i = 0; i < jarFiles.length; i++) {
			URL url = null;
			try {
				url = jarFiles[i].toURI().toURL();
			} catch (MalformedURLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			urls[i] = url;
		}
//		ClassLoader cl = new URLClassLoader(urls);
		
		try {
			ClassLoader threadCL = Thread.currentThread().getContextClassLoader();
			ClassLoader cl = new URLClassLoader(urls, threadCL);
			logger.debug("Extranal Components classloader:" + getClassloaderHierarchy(cl));
			logger.debug("Thread classloader:" + getClassloaderHierarchy(threadCL));
			logger.debug("ValidateFailedException is in classloader:" + getClassloaderHierarchy(ValidateFailedException.class.getClassLoader()));
			Class componentClass = cl.loadClass(component.getClassName());
			componentObj =  componentClass.newInstance();
			String[] args =  component.getInitMethod().getParams().toStringArray();
			Map mapArgs = component.getInitMethod().getParams().toMap();
			try {
			    MethodUtils.invokeMethod(componentObj, component.getInitMethod().getName(), args);
			} catch(NoSuchMethodException e) {
			    MethodUtils.invokeMethod(componentObj, component.getInitMethod().getName(), mapArgs);
			}
			logger.info("initialized external component:" + component.getName());
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			logger.info("initialized external component:" + component.getName() + "failed");
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			logger.info("initialized external component:" + component.getName() + "failed");
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			logger.info("initialized external component:" + component.getName() + "failed");
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			logger.info("initialized external component:" + component.getName() + "failed");
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			logger.info("initialized external component:" + component.getName() + "failed");
		}
		
		return componentObj;
	}
	private Object initBuildinComponent(Component component) {
		Class componentClass = null;
		Object componentObj = null;
//		try {
//			
//			componentClass = this.getClass().getClassLoader().loadClass(component.getClassName());
//		} catch (ClassNotFoundException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		try {
//			componentObj =  componentClass.newInstance();
			String type = component.getType();
			if(type == null) {
				type = component.getName();
			}
			componentObj = context.getBean(type);
			InitMethod initMethod = component.getInitMethod();
			if (initMethod != null) {
				List<Param> paramList = component.getInitMethod().getParams().getParam();
				String[] args = new String[paramList.size()];
				for (int i = 0; i < paramList.size(); i++) {
					args[i] = paramList.get(i).getValue();
				}
				MethodUtils.invokeMethod(componentObj, component.getInitMethod().getName(), args);
			}
			
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return componentObj;
	}
	
	/**
	public Object createSimulator(Device dev) {
		try {
			if(dev.getType().equals("Build-In")) {
				 Class simulatorClass = this.getClass().getClassLoader().loadClass(dev.getClassName());
				 Object simulator =  simulatorClass.newInstance();
				 return simulator;
			}
			 File file = new File(simulatorDir + File.separator + dev.getType());
			 if(!file.exists()) {
				 logger.info("The simualtor type " + dev.getType() + " directory doesn't exist!");
				 return null;
			 }
			 if(!file.isDirectory()) {
				 logger.info("The configuration of simualtor directory is not a directory!");
				 return null;
			 }
			 File[] jarFiles = file.listFiles();
			 String[] libFiles = file.list();
			 URL[] urls = new URL[libFiles.length]; 
			 for(int i = 0; i < jarFiles.length; i++) {
				 URL url = jarFiles[i].toURI().toURL();
				 urls[i] = url;
			 }
			 ClassLoader cl = new URLClassLoader(urls);
			 Class simulatorClass = cl.loadClass(dev.getClassName());
			 
			 Object simulator =  simulatorClass.newInstance();
			 return simulator;
		} catch(Exception e) {
			e.printStackTrace();
		}

		
		return null;
	}**/
	
	public void loadComponentDefinition() {
		loadBuildinComponentDefinition(buildinCompConfig);
		loadExternalComponentDefinition(componentDir);
	}
	
	public void loadBuildinComponentDefinition(String buildinCompConfig) {
		Components components = componentReader.readComponents(buildinCompConfig);
		if(components == null) {
			logger.error("Load buildin components failed:" + buildinCompConfig);
			return;
		}
		List<Component> componentList = components.getComponent();
		for(int i = 0; i < componentList.size(); i++) {
			Component tmp = componentList.get(i);
			tmp.setBuildin(true);
			tmp.setDefinitionFile(buildinCompConfig);
			componentDefinitionMap.put(tmp.getName(), tmp);
		}
	}
	
	public void loadExternalComponentDefinition(String componentDir) {
		File file = new File(componentDir);
		if(!file.exists()) {
			return;
		}
		if(!file.isDirectory()) {
			logger.error("componentDir is not a directory:" + componentDir);
			return;
		} 
		String[] fileList = file.list();
		for (int i = 0; i < fileList.length; i++) {
			String fileName = componentDir + File.separator + fileList[i] + File.separator + "Components.xml";
			Components components = componentReader.readComponents(fileName);
			List<Component> componentList = components.getComponent();
			for (int j = 0; j < componentList.size(); j++) {
				Component tmp = componentList.get(j);
				tmp.setDefinitionFile(fileName);
				componentDefinitionMap.put(tmp.getName(), tmp);
			}
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		// TODO Auto-generated method stub
		context = applicationContext;
	}
	
}
